winston과 winston-daily-rotate-file
✒️ 2025-05-26 14:14 내용 수정
Node.js 교과서 개정 3판 내용 정리
1. winston
실제 서버 운영 시 console.log와 console.error를 대체하는 모듈
- 공식 문서 : https://www.npmjs.com/package/winston
- 참고 자료 : reflectoring Node.js Logging with Winston
- 서버를 배포하면 서버 종료 시 로그들이 함께 사라지기 때문에 로그를 파일이나 DB에 저장하기 위해 사용한다.
npm install winston
- 모듈 파일인
logger.js를 작성한다.level: 로그의 심각도- error, warn, info, verbose, debug, silly가 있다.
format: 로그의 형식- json, label, timestamp, printf, simple, combine 등이 있다.
- 보통은 json을 사용하며, 로그 기록 시간을 표시하려면 timestamp를 사용한다.
transports: 로그 저장 방식new transports.File: 파일로 저장new transports.Console: 콘솔에 출력
// logger.js
const { createLogger, format, transports } = require('winston');
// 파일로 저장
const log_file = new transports.File({ filename : 'serverlog.log', level: 'info' });
const error_file = new transports.File({ filename : 'error.log' , level: 'error' });
// 콘솔로 출력
const log_console = new transports.Console({ format: format.simple() });
// logger 설정
const logger = createLogger({
level: 'info', // 로그의 심각도
format: format.json(), // 로그 형식
transports: [ // 로그 저장 방식
log_file, error_file
]
});
// 배포 환경이 아닐 때 설정
if (process.env.NODE_ENV !== 'production') { // 파일도 저장하고 콘솔로도 출력
logger.add(log_console); // 콘솔 출력용 transport 추가
}
module.exports = logger;
- 이제 서버 파일(server.js 등)에서 module을 가져와서 적용한다.
- 로깅 메시지를 요청와 url로 만들어 로깅 함수가 메시지를 저장하도록 설정한다.
- 만약 에러가 존재한다면 에러 메시지를 기록하도록 한다.
- middleware 체인의 다음 함수로 넘어가기 위해
next()를 호출한다.
// server.js
// winston - logger : log 저장용
const logger = require('./lib/logger.js');
app.use((req, res, next)=>{
// 로그 메시지 생성
const logMessage = `Request : ${req.method} ${req.url}`;
// 요청을 로깅
logger.log({
level: 'info',
message: logMessage
});
// 에러가 있으면 에러 로깅
if (req.error) {
logger.error({
level: 'error',
message: req.error.message
});
}
next();
});
2. winston-daily-rotate-file
winston과 함께 로그 파일을 관리하는 모듈
- 공식 문서 : https://www.npmjs.com/package/winston-daily-rotate-file
- 로그 파일을 시간 단위로 관리할 수 있고, 파일의 용량 및 개수를 관리할 수 있다.
- 위의
logger.js파일을winston-daily-rotate-file을 적용하면서 로그 파일에 대한 상세 정보를 추가하여 수정했다.
// logger.js
const winston = require('winston');
const winstonDaily = require('winston-daily-rotate-file');
const process = require('process'); // root 경로를 가져오기 위함
// 날짜, 시스템이름(label), 형식, 출력 정의함수(printf), transport 등을 가져옴
const { createLogger, format, transports } = winston;
const { combine, timestamp, label, printf } = format;
// 로그파일 저장경로 : root경로/logs
const logDir = `${process.cwd()}/server/logs`;
// 출력 형식
const logFormat = printf(({ level, message, label, timestamp}) => {
// 날짜 시스템이름 로그레벨 메시지 순서로 출력 지정
return `${timestamp} [${label}] ${level} ${message}`;
});
// 파일로 저장
const log_file = new winstonDaily({ // 일반 로그
level: 'info', // info 레벨
datePattern: 'YYYY-MM-DD', // 파일의 날짜 형식
dirname: logDir, // 저장경로
filename : `%DATE%.log`, // 파일이름 - 날짜형식
maxFiles: 30, // 30일치 로그파일 저장
zippedArchive: true // 로그파일을 gzip으로 압축할지 여부
});
const error_file = new winstonDaily({ // 에러
level: 'error',
datePattern: 'YYYY-MM-DD',
dirname: logDir + '/error', // /logs/error 폴더에 저장
filename : `%DATE%.error.log`,
maxFiles: 30,
zippedArchive: true
});
const exception_file = new winstonDaily({ // 예외처리
level: 'error',
datePattern: 'YYYY-MM-DD',
dirname: logDir,
filename : `%DATE%.exception.log`,
maxFiles: 30,
zippedArchive: true
});
// logger 설정
const logger = createLogger({
level: 'info', // 로그의 심각도
format: combine( // 로그 형식
timestamp({ format : 'YYYY-MM-DD HH:mm:ss' }),
label({ label : 'test' }),
logFormat
),
transports: [ // 로그 저장 방식 - 파일 저장
log_file, error_file
],
exceptionHandlers: [ // 예외처리 발생 시 설정
exception_file
]
});
// 배포 환경이 아닐 때 설정
if (process.env.NODE_ENV !== 'production') { // 파일도 저장하고 콘솔로도 출력
logger.add(
new transports.Console({
format: format.combine( // log 설정을 여러 개 적용하기 위한 combine 설정
format.colorize(), // level별 색상 적용
format.simple() // '레벨: 메시지 json' 형식으로 출력
)
})
);
}
module.exports = logger;
- 이제
server.js에서logger.js모듈을 가져와 사용하도록 설정하고 서버를 실행해본다.
// server.js
const logger = require('./lib/logger.js');
app.use((req, res, next)=>{
// 로그 메시지 생성
const logMessage = `Request : ${req.method} ${req.url}`;
// 요청을 로깅
logger.log({
level: 'info',
message: logMessage
});
// 에러가 있으면 에러 로깅
if (req.error) {
logger.error({
level: 'error',
message: req.error.message
});
}
next();
});
- 개발 환경에서 console에 메시지가 정상적으로 뜬다.
- 배포하려는 프로젝트에서 테스트해서 라우트는 프로젝트 라우트로 뜬다.
- 서버 실행 시 루트 경로의
/server폴더 내에/logs와/logs/error폴더가 자동 생성되고, 파일들이 저장된다.
- json 파일을 열어보면 로그와 관련된 정보가 저장되어 있다.
- log 파일을 열었을 때
logFormat에서 지정해준 형식대로 로그가 기록되어 있다.
console function override
winston을 적용하고 다른 코드를 개발 환경에서 테스트하던 중console.log()가 아예 작동하지 않는 문제가 발생했다.- 원인을 찾아보니
winston의logger에서transports.Console()을 적용해서console.log()를 대체하도록 설정되었기 때문이라 한다. - 개발 환경에서
winston과console을 둘 다 사용하기 위해console의log,error함수를 override하도록 설정을 변경했다.
// logger.js
// 위 코드는 동일하므로 생략
// 배포 환경이 아닐 때 설정
if (process.env.NODE_ENV !== 'production') { // 파일도 저장하고 콘솔로도 출력
logger.add(
new transports.Console({
format: format.combine( // log 설정을 여러 개 적용하기 위한 combine 설정
format.colorize(), // level별 색상 적용
format.simple() // '레벨: 메시지 json' 형식으로 출력
)
})
);
// console 함수 오버라이드 - logger를 호출하도록 변경
console.log = function(...args) {
return logger.info.call(logger, ...args);
}
console.error = function(...args) {
return logger.error.call(logger, ...args);
}
console.info = function(...args) {
return logger.warn.call(logger, ...args);
}
}
module.exports = logger;
- 만들어둔 API에서
console.log()를 호출해서 터미널의 결과를 확인했다.
// api test
router.get('/search/product', async (req, res) => {
const { keyword } = req.query;
console.log('test', req.query);
// 생략
});
-
console.log('test', req.query)대신winston으로 만든logger를 통해test와{"keyword":"sad"}가 출력되는 것을 확인할 수 있다.
-
개발 환경에선 로그 파일도 저장하고 있는데, 로그 파일에도 test가 같이 기록된 것을 확인할 수 있다. 다만 두 번째 args는 기록이 안되어 있다.
- 단순 확인용
console.log()라면 로그 파일에 추가 여부를 나중에 선택해도 될 것 같다.
- 단순 확인용